﻿// Copyright (c) 2010 Xomega.Net. All rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Xomega.Framework.Lookup;

namespace Xomega.Framework.Properties
{
    /// <summary>
    /// A data property that has enumerated set of possible values
    /// that come from a lookup table of the specified type.
    /// Internally the values are stored as objects of type <see cref="Header"/>,
    /// which can store ID, text and a number of additional attributes for the value.
    /// When a value is being set to the property it tries to resolve it to a Header
    /// by looking it up in the lookup table for the property, which is obtained
    /// from a lookup cache of a given type.
    /// </summary>
    public class EnumProperty : DataProperty<Header>
    {
        /// <summary>
        /// The type of cache to use to obtain the lookup table.
        /// By default the <see cref="LookupCache.Global"/> cache is used, which is shared across the whole application.
        /// </summary>
        public string CacheType { get; set; }

        /// <summary>
        /// Enumeration type, which is the type of a lookup table in the cache to be used for the property.
        /// This field should be initialized externally by either a subclass property
        /// or from within the data object.
        /// </summary>
        public string EnumType { get; set; }

        /// <summary>
        /// The string format that is used to obtain the key field from the Header.
        /// The default value points to the header ID (see <see cref="Header.FieldId"/>),
        /// but it can be customized to point to another unique field or a combination of fields
        /// in the header, e.g. a custom attribute that stores a unique abbreviation.
        /// </summary>
        public string KeyFormat { get; set; }

        /// <summary>
        /// The string format for a header field or combination of fields that is used
        /// to display the header as a string. The default value is to display the header text
        /// (see <see cref="Header.FieldText"/>).
        /// </summary>
        public string DisplayFormat { get; set; }

        /// <summary>
        ///  Constructs an EnumProperty with a given name and adds it to the parent data object under this name.
        /// </summary>
        /// <param name="parent">The parent data object to add the property to if applicable.</param>
        /// <param name="name">The property name that should be unique within the parent data object.</param>
        public EnumProperty(DataObject parent, string name)
            : base(parent, name)
        {
            CacheType = LookupCache.Global;
            KeyFormat = Header.FieldId;
            DisplayFormat = Header.FieldText;
            ItemsProvider = GetItems;
            FilterFunc = IsAllowed;
            SortField = DefaultSortField;
        }

        /// <summary>
        /// Gets the lookup table for the property. The default implementation uses the <see cref="EnumType"/>
        /// to find the lookup table in the lookup cache specified by the <see cref="CacheType"/>.
        /// Subclasses can override this behavior to get a lookup table from a local class member or elsewhere.
        /// </summary>
        /// <returns>The lookup table to be used for the property.</returns>
        protected virtual LookupTable GetLookupTable()
        {
            LookupCache cache = LookupCache.Get(CacheType);
            return cache == null ? null : cache.GetLookupTable(EnumType);
        }

        /// <summary>
        /// Converts a single value to a given format. For internal format
        /// this method tries to convert the value to a header by looking it up
        /// in the lookup table. For the transport format it uses the header ID.
        /// For DisplayString and EditString formats it displays the header formatted according
        /// to the specified <see cref="DisplayFormat"/> or <see cref="KeyFormat"/> respectively.
        /// </summary>
        /// <param name="value">A single value to convert to the given format.</param>
        /// <param name="format">The value format to convert the value to.</param>
        /// <returns>The value converted to the given format.</returns>
        protected override object ConvertValue(object value, ValueFormat format)
        {
            Header h = value as Header;
            if (format == ValueFormat.Internal)
            {
                if (h != null && h.Type == EnumType) return value;
                string str = Convert.ToString(value);
                LookupTable tbl = GetLookupTable();
                if (tbl != null)
                {
                    h = null;
                    if (KeyFormat != Header.FieldId) h = tbl.LookupByFormat(KeyFormat, str);
                    if (h == null) h = tbl.LookupById(str);
                    if (h != null)
                    {
                        h.DefaultFormat = KeyFormat;
                        return h;
                    }
                }
                return new Header(CacheType, str);
            }
            else if (h != null)
            {
                if (format == ValueFormat.Transport) return h.Id;
                if (format == ValueFormat.EditString) return h.ToString(KeyFormat);
                if (format == ValueFormat.DisplayString) return h.ToString(DisplayFormat);
            }
            return base.ConvertValue(value, format);
        }

        /// <summary>
        /// A function that is used by default as the possible items provider
        /// for the property by getting all possible values from the lookup table
        /// filtered by the specified filter function if any and ordered by
        /// the specified SortField function if any.
        /// </summary>
        /// <param name="input">The user input so far.</param>
        /// <returns>A list of possible values.</returns>
        protected virtual IEnumerable GetItems(object input)
        {
            LookupTable tbl = GetLookupTable();
            if (tbl != null)
            {
                IEnumerable<Header> res = tbl.GetValues(FilterFunc);
                if (SortField != null) res = res.OrderBy(SortField);
                foreach (Header h in res)
                {
#if SILVERLIGHT
                    // use display format as default for Silverlight, since DataPropertyItemBinding is not supported
                    h.DefaultFormat = DisplayFormat;
#else
                    // use key format as default for WPF for editable combo boxes, since there seems to be
                    // no other way to control it on the framework level
                    h.DefaultFormat = KeyFormat;
#endif
                    yield return h;
                }
            }
        }

        /// <summary>
        /// Checks if the given header is an allowed possible value.
        /// By default all active headers are allowed.
        /// This is the default filter function.
        /// </summary>
        /// <param name="h"></param>
        /// <returns></returns>
        public virtual bool IsAllowed(Header h)
        {
            return h != null && h.IsActive;
        }

        /// <summary>
        /// A function to filter allowed items.
        /// By default only active items are allowed.
        /// </summary>
        public Func<Header, bool> FilterFunc;

        /// <summary>
        /// A function that extracts an item field to be used for sorting.
        /// By default items are sorted by their display string.
        /// </summary>
        public Func<Header, object> SortField;

        /// <summary>
        /// Returns a value or a combination of values from the given header
        /// that should be used for sorting headers between each other.
        /// This is a default SortField function that sorts headers by their display string.
        /// </summary>
        /// <param name="h">The header from which to get the sort field.</param>
        /// <returns>A value or a combination of values from the given header
        /// that should be used for sorting headers between each other.</returns>
        public object DefaultSortField(Header h)
        {
            object cval = h;
            if (ValueConverter == null || !ValueConverter(ref cval, ValueFormat.DisplayString))
                cval = ConvertValue(cval, ValueFormat.DisplayString);
            return cval;
        }
    }
}
